#include "SpecificationTreeModel.h"

#include "LeIpc2581/Spec.h"
#include "LeIpc2581/Property.h"
#include "LeIpc2581/enumtranslator.h"

#include <QDebug>
#include <QMultiMap>

class SpecificationTreeItem
{
public:
    SpecificationTreeItem(const QString &name = QString(), const QString value = QString()):
        m_name(name), m_value(value), m_parent(nullptr)
    {
    }

    ~SpecificationTreeItem()
    {
        qDeleteAll(m_children);
    }

    inline void addChild(SpecificationTreeItem *item)
    {
        item->m_parent = this;
        m_children.append(item);
    }

    inline SpecificationTreeItem *child(int row) const
    {
        return m_children.value(row);
    }

    inline int childCount() const
    {
        return m_children.count();
    }

    inline int row() const
    {
        if (m_parent != nullptr)
            return m_parent->m_children.indexOf(const_cast<SpecificationTreeItem*>(this));
        return 0;
    }

    inline SpecificationTreeItem *parent() const
    {
        return m_parent;
    }

    inline QString name() const
    {
        return m_name;
    }

    inline QString value() const
    {
        return m_value;
    }
private:
    QString m_name;
    QString m_value;
    SpecificationTreeItem* m_parent;
    QList<SpecificationTreeItem*> m_children;
};

// cppcheck-suppress noCopyConstructor
class SpecificationTreeModelPrivate
{
    Q_DISABLE_COPY(SpecificationTreeModelPrivate)
    Q_DECLARE_PUBLIC(SpecificationTreeModel)
    SpecificationTreeModel * const q_ptr;

public:
    SpecificationTreeModelPrivate(SpecificationTreeModel *model, const QList<Ipc2581b::Spec *> specList);
    ~SpecificationTreeModelPrivate()
    {
        delete m_root;
    }

    QString formatProperty(const Ipc2581b::Property *property);

private:
    QList<Ipc2581b::Spec*> m_specList;
    SpecificationTreeItem *m_root;
};

SpecificationTreeModel::SpecificationTreeModel(const QList<Ipc2581b::Spec *> specList, QObject *parent):
    QAbstractItemModel(parent),
    d_ptr(new SpecificationTreeModelPrivate(this, specList))
{
}

SpecificationTreeModel::~SpecificationTreeModel()
{

}

QModelIndex SpecificationTreeModel::index(const QString &specificationKey) const
{
    Q_D(const SpecificationTreeModel);

    for (int row = 0; row < d->m_root->childCount(); row++)
    {
        SpecificationTreeItem *item = d->m_root->child(row);
        if (item->name() == specificationKey)
            return createIndex(row, 0, item);
    }
    return QModelIndex();
}

QVariant SpecificationTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation != Qt::Horizontal)
        return QVariant();

    if (role != Qt::DisplayRole)
        return QVariant();

    switch (section)
    {
        case NameColumn:
            return "Name";
        case ValueColumn:
            return "Value";
        default:
            return QVariant();
    }
}

QModelIndex SpecificationTreeModel::index(int row, int column, const QModelIndex &parent) const
{
    Q_D(const SpecificationTreeModel);

    if (!hasIndex(row, column, parent))
        return QModelIndex();

    SpecificationTreeItem *parentItem;

    if (!parent.isValid())
        parentItem = d->m_root;
    else
        parentItem = static_cast<SpecificationTreeItem*>(parent.internalPointer());

    SpecificationTreeItem *childItem = parentItem->child(row);
    if (childItem)
        return createIndex(row, column, childItem);
    else
        return QModelIndex();
}

QModelIndex SpecificationTreeModel::parent(const QModelIndex &index) const
{
    Q_D(const SpecificationTreeModel);

    if (!index.isValid())
        return QModelIndex();

    SpecificationTreeItem *childItem = static_cast<SpecificationTreeItem*>(index.internalPointer());
    SpecificationTreeItem *parentItem = childItem->parent();

    if (parentItem == d->m_root)
        return QModelIndex();

    return createIndex(parentItem->row(), 0, parentItem);
}

int SpecificationTreeModel::rowCount(const QModelIndex &parent) const
{
    Q_D(const SpecificationTreeModel);

    SpecificationTreeItem *parentItem;
    if (parent.column() > 0)
        return 0;

    if (!parent.isValid())
        parentItem = d->m_root;
    else
        parentItem = static_cast<SpecificationTreeItem*>(parent.internalPointer());

    return parentItem->childCount();
}

int SpecificationTreeModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return ColumnCount;
}

QVariant SpecificationTreeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role != Qt::DisplayRole && role != Qt::EditRole)
        return QVariant();

    SpecificationTreeItem *item = static_cast<SpecificationTreeItem*>(index.internalPointer());

    switch (index.column()) {
        case NameColumn:
            return item->name();
        case ValueColumn:
            return item->value();
        default:
            return QVariant();
    }
}

SpecificationTreeModelPrivate::SpecificationTreeModelPrivate(SpecificationTreeModel *model, const QList<Ipc2581b::Spec *> specList):
    q_ptr(model), m_specList(specList)
{
    m_root = new SpecificationTreeItem();
    for (const Ipc2581b::Spec *spec: m_specList)
    {
        auto specItem = new SpecificationTreeItem(spec->name);
        m_root->addChild(specItem);

        QMap<QString, SpecificationTreeItem*> categoryByName;
        QMultiMap<SpecificationTreeItem*, SpecificationTreeItem*> itemByCategory;
        for (const Ipc2581b::SpecificationType *specType: spec->specificationTypeList)
        {
            switch (specType->specificationTypeType())
            {
                case Ipc2581b::SpecificationType::SpecificationTypeType::Backdrill:
                {
                    const Ipc2581b::Backdrill *backdrill = specType->toBackdrill();
                    const QString categoryName = QStringLiteral("Backdrill");
                    if (!categoryByName.contains(categoryName))
                        categoryByName.insert(categoryName, new SpecificationTreeItem(categoryName));
                    auto categoryItem = categoryByName.value(categoryName);
                    for (const Ipc2581b::Property *property: backdrill->propertyList)
                    {
                        QString name = Ipc2581b::EnumTranslator::backdrillListText(backdrill->type);
                        if (backdrill->type == Ipc2581b::BackdrillList::Other)
                            name = backdrill->commentOptional.value;
                        auto propertyItem = new SpecificationTreeItem(name, formatProperty(property));
                        itemByCategory.insertMulti(categoryItem, propertyItem);
                    }
                    break;

                }
                case Ipc2581b::SpecificationType::SpecificationTypeType::Compliance:
                {
                    const Ipc2581b::Compliance *compliance = specType->toCompliance();
                    const QString categoryName = QStringLiteral("Compliance");
                    if (!categoryByName.contains(categoryName))
                        categoryByName.insert(categoryName, new SpecificationTreeItem(categoryName));
                    auto categoryItem = categoryByName.value(categoryName);
                    for (const Ipc2581b::Property *property: compliance->propertyList)
                    {
                        QString name = Ipc2581b::EnumTranslator::complianceListText(compliance->type);
                        if (compliance->type == Ipc2581b::ComplianceList::Other)
                            name = compliance->commentOptional.value;
                        auto propertyItem = new SpecificationTreeItem(name, formatProperty(property));
                        itemByCategory.insertMulti(categoryItem, propertyItem);
                    }
                    break;
                }
                case Ipc2581b::SpecificationType::SpecificationTypeType::Conductor:
                {
                    const Ipc2581b::Conductor *conductor = specType->toConductor();
                    const QString categoryName = QStringLiteral("Conductor");
                    if (!categoryByName.contains(categoryName))
                        categoryByName.insert(categoryName, new SpecificationTreeItem(categoryName));
                    auto categoryItem = categoryByName.value(categoryName);
                    for (const Ipc2581b::Property *property: conductor->propertyList)
                    {
                        QString name = Ipc2581b::EnumTranslator::conductorListText(conductor->type);
                        if (conductor->type == Ipc2581b::ConductorList::Other)
                            name = conductor->commentOptional.value;
                        auto propertyItem = new SpecificationTreeItem(name, formatProperty(property));
                        itemByCategory.insertMulti(categoryItem, propertyItem);
                    }
                    break;
                }
                case Ipc2581b::SpecificationType::SpecificationTypeType::Dielectric:
                {
                    const Ipc2581b::Dielectric *dielectric = specType->toDielectric();
                    const QString categoryName = QStringLiteral("Dielectric");
                    if (!categoryByName.contains(categoryName))
                        categoryByName.insert(categoryName, new SpecificationTreeItem(categoryName));
                    auto categoryItem = categoryByName.value(categoryName);
                    for (const Ipc2581b::Property *property: dielectric->propertyList)
                    {
                        QString name = Ipc2581b::EnumTranslator::dielectricListText(dielectric->type);
                        if (dielectric->type == Ipc2581b::DielectricList::Other)
                            name = dielectric->commentOptional.value;
                        auto propertyItem = new SpecificationTreeItem(name, formatProperty(property));
                        itemByCategory.insertMulti(categoryItem, propertyItem);
                    }
                    break;
                }
                case Ipc2581b::SpecificationType::SpecificationTypeType::General:
                {
                    const Ipc2581b::General *general = specType->toGeneral();
                    const QString categoryName = QStringLiteral("General");
                    if (!categoryByName.contains(categoryName))
                        categoryByName.insert(categoryName, new SpecificationTreeItem(categoryName));
                    auto categoryItem = categoryByName.value(categoryName);
                    for (const Ipc2581b::Property *property: general->propertyList)
                    {
                        QString name = Ipc2581b::EnumTranslator::generalListText(general->type);
                        if (general->type == Ipc2581b::GeneralList::Other)
                            name = general->commentOptional.value;
                        auto propertyItem = new SpecificationTreeItem(name, formatProperty(property));
                        itemByCategory.insertMulti(categoryItem, propertyItem);
                    }
                    break;
                }
                case Ipc2581b::SpecificationType::SpecificationTypeType::Impedance:
                {
                    const Ipc2581b::Impedance *impedance = specType->toImpedance();
                    const QString categoryName = QStringLiteral("Impedance");
                    if (!categoryByName.contains(categoryName))
                        categoryByName.insert(categoryName, new SpecificationTreeItem(categoryName));
                    auto categoryItem = categoryByName.value(categoryName);
                    for (const Ipc2581b::Property *property: impedance->propertyList)
                    {
                        QString name = QString("%1, %2, %3")
                                .arg(Ipc2581b::EnumTranslator::impedanceListText(impedance->type))
                                .arg(Ipc2581b::EnumTranslator::structureListText(impedance->structure))
                                .arg(Ipc2581b::EnumTranslator::transmissionListText(impedance->transmission));
                        if (impedance->type == Ipc2581b::ImpedanceList::Other)
                            name = impedance->commentOptional.value;
                        auto propertyItem = new SpecificationTreeItem(name, formatProperty(property));
                        itemByCategory.insertMulti(categoryItem, propertyItem);
                    }
                    break;
                }
                case Ipc2581b::SpecificationType::SpecificationTypeType::Technology:
                {
                    const Ipc2581b::Technology *technology = specType->toTechnology();
                    const QString categoryName = QStringLiteral("Technology");
                    if (!categoryByName.contains(categoryName))
                        categoryByName.insert(categoryName, new SpecificationTreeItem(categoryName));
                    auto categoryItem = categoryByName.value(categoryName);
                    for (const Ipc2581b::Property *property: technology->propertyList)
                    {
                        QString name = Ipc2581b::EnumTranslator::technologyListText(technology->type);
                        if (technology->type == Ipc2581b::TechnologyList::Other)
                            name = technology->commentOptional.value;
                        auto propertyItem = new SpecificationTreeItem(name, formatProperty(property));
                        itemByCategory.insertMulti(categoryItem, propertyItem);
                    }
                    break;
                }
                case Ipc2581b::SpecificationType::SpecificationTypeType::Temperature:
                {
                    const Ipc2581b::Temperature *temperature = specType->toTemperature();
                    const QString categoryName = QStringLiteral("Temperature");
                    if (!categoryByName.contains(categoryName))
                        categoryByName.insert(categoryName, new SpecificationTreeItem(categoryName));
                    auto categoryItem = categoryByName.value(categoryName);
                    for (const Ipc2581b::Property *property: temperature->propertyList)
                    {
                        QString name = Ipc2581b::EnumTranslator::temperatureListText(temperature->type);
                        if (temperature->type == Ipc2581b::TemperatureList::Other)
                            name = temperature->commentOptional.value;
                        auto propertyItem = new SpecificationTreeItem(name, formatProperty(property));
                        itemByCategory.insertMulti(categoryItem, propertyItem);
                    }
                    break;
                }
                case Ipc2581b::SpecificationType::SpecificationTypeType::Tool:
                {
                    const Ipc2581b::Tool *tool = specType->toTool();
                    const QString categoryName = QStringLiteral("Tool");
                    if (!categoryByName.contains(categoryName))
                        categoryByName.insert(categoryName, new SpecificationTreeItem(categoryName));
                    auto categoryItem = categoryByName.value(categoryName);
                    for (const Ipc2581b::Property *property: tool->propertyList)
                    {
                        QString name = QString("%1, %2, %3")
                                .arg(Ipc2581b::EnumTranslator::toolListText(tool->type))
                                .arg(Ipc2581b::EnumTranslator::toolPropertyListText(tool->toolProperty));
                        if (tool->toolProperty == Ipc2581b::ToolPropertyList::Other)
                            name = QString("%1, %2, %3")
                                    .arg(Ipc2581b::EnumTranslator::toolListText(tool->type))
                                    .arg(tool->commentOptional.value);
                        auto propertyItem = new SpecificationTreeItem(name, formatProperty(property));
                        itemByCategory.insertMulti(categoryItem, propertyItem);
                    }
                    break;
                }
                case Ipc2581b::SpecificationType::SpecificationTypeType::V_Cut:
                {
                    const Ipc2581b::V_Cut *vcut = specType->toV_Cut();
                    const QString categoryName = QStringLiteral("Temperature");
                    if (!categoryByName.contains(categoryName))
                        categoryByName.insert(categoryName, new SpecificationTreeItem(categoryName));
                    auto categoryItem = categoryByName.value(categoryName);
                    for (const Ipc2581b::Property *property: vcut->propertyList)
                    {
                        QString name = Ipc2581b::EnumTranslator::vCutListText(vcut->type);
                        if (vcut->type == Ipc2581b::VCutList::Other)
                            name = vcut->commentOptional.value;
                        auto propertyItem = new SpecificationTreeItem(name, formatProperty(property));
                        itemByCategory.insertMulti(categoryItem, propertyItem);
                    }
                    break;
                }
            }
        }

        for (auto categoryItem: categoryByName.values())
        {
            specItem->addChild(categoryItem);
            for (auto propertyItem: itemByCategory.values(categoryItem))
            {
                categoryItem->addChild(propertyItem);
            }
        }
    }
}

QString SpecificationTreeModelPrivate::formatProperty(const Ipc2581b::Property *property)
{
    if (property->layerOrGroupRefOptional.hasValue)
        return property->layerOrGroupRefOptional.value;

    QString value;
    if (property->valueOptional.hasValue)
        value = QString::number(property->valueOptional.value);
    else if (property->textOptional.hasValue)
        value = property->textOptional.value;
    else
        value = QStringLiteral("?");
    QString unit;
    if (property->unitOptional.hasValue)
        unit = Ipc2581b::EnumTranslator::propertyUnitText(property->unitOptional.value);
    QString tol;
    if (property->tolMinusOptional.hasValue && property->tolPlusOptional.hasValue &&
            qFuzzyCompare(property->tolMinusOptional.value, property->tolPlusOptional.value))
    {
        tol = QString("±%1").arg(property->tolMinusOptional.value);
    }
    else if (property->minusTolOptional.hasValue && property->plusTolOptional.hasValue &&
             qFuzzyCompare(property->minusTolOptional.value, property->plusTolOptional.value))
    {
        tol = QString("±%1").arg(property->plusTolOptional.value);
    }
    else
    {
        QStringList tolPair;
        if (property->tolMinusOptional.hasValue)
            tolPair.append(QString("%1").arg(property->tolMinusOptional.value));
        if (property->tolPlusOptional.hasValue)
            tolPair.append(QString("%1").arg(property->tolPlusOptional.value));
        if (property->minusTolOptional.hasValue)
            tolPair.append(QString("%1").arg(property->minusTolOptional.value));
        if (property->plusTolOptional.hasValue)
            tolPair.append(QString("%1").arg(property->plusTolOptional.value));
        tol = tolPair.join("/");
    }
    if (property->tolPercentOptional.hasValue && property->tolPercentOptional.value &&
            !tol.isEmpty())
        tol += "%";

    QString refValue;
    if (property->refValueOptional.hasValue)
        refValue = property->refValueOptional.value;
    else if (property->refTextOptional.hasValue)
        refValue = property->refTextOptional.value;
    QString refUnit;
    if (property->refUnitOptional.hasValue)
        refUnit = Ipc2581b::EnumTranslator::propertyUnitText(property->refUnitOptional.value);

    QString comment;
    if (property->commentOptional.hasValue)
        comment = property->commentOptional.value;

    QString result = value;
    if (!unit.isEmpty())
        result.append(QString(" %1").arg(unit));
    if (!tol.isEmpty())
        result.append(QString(", %1").arg(tol));
    if (!refValue.isEmpty())
        result.append(QString(", @%1").arg(refValue));
    if (!refUnit.isEmpty())
        result.append(QString(" %1").arg(refUnit));
    return result;
}
